home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Netware Super Library
/
Netware Super Library.iso
/
pgm_tool
/
pprd100
/
pprd.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-07-07
|
20KB
|
807 lines
/*
PPRD
Line printer daemon using Berkeley LPD protocol on top of WATTCP for up
to 3 parallel printer ports.
This daemon is written as a state machine because it has to support
several connections on a single threaded system, DOS.
When started up, it checks to see how many printers are known to the
BIOS. These printers are served as LPT[123]. It listens on the
standard LPD port 515, but this can be changed from the command line.
Examples of options:
-p1515 listen on port 1515 instead
-j9200 use direct protocol instead at port 9100 (defaults to 9100 if unspec)
-23 disable printers 2 and 3
-n2 two printers, no matter what BIOS claims
-b12 on 1 and 2 bypass BIOS and send directly to port (parallel ports only)
-t don't indicate available printers with tones
-i reinitialise printer via hardware line on job abort
-s disable subnet match (server and client must be on same subnet)
-alist comma separated list of up to 20 domain names allowed connection
-dlist comma separated list of up to 20 domain names denied connection
last two are mutually exclusive and independent of subnet check
-lhost log diagnostics to host of that domain name
Jobs can be aborted by C-F1 through C-F3 for that printer.
The LPD protocol is summarised below where S = server and C = client.
Ack = '\0' and nak = '\001'.
C: \002printer\n
S: ack/nak
C: \003size datafilename\n (size is bytes as decimal string)
S: ack/nak
C: size bytes of data + one '\0' byte
S: ack/nak
C: \002size cntlfilename\n
S: ack/nak
C: size bytes of control + one '\0' byte
S: ack/nak
The data and control files can occur in either order. In this
implementation the filenames are ignored.
This program was compiled under Borland C++ 3.1 in C mode for the small
model. You will require the WATTCP libraries. A copy is included in the
distribution. For the sources of WATTCP, ask archie where the archives
are.
Please send bug fixes, ports and enhancements to the author for
incorporation in newer versions.
Copyright (C) 1995 Ken Yap (ken@syd.dit.csiro.au)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <sys/types.h>
#include <time.h>
#include <tcp.h>
#include <dos.h>
#include <bios.h>
#include <conio.h>
#include "pprd.h"
#define PROGRAM "PPRD"
#define VERSION "Version 1.00 15 Jul 1995"
#define AUTHOR "Copyright (C) 1995 Ken Yap (ken@syd.dit.csiro.au)"
#define MAXLPT 3 /* supported under DOS */
#ifdef unix /* test implementation */
#define MAXCON 1
#else
/* for lpr, 1 for lpq, and direct */
#define MAXCON (MAXLPT+1)
#endif
/* try this many times to see if can accept next byte right away */
#define POLL_MAX 10
unsigned int lpdport = LPDPORT;
unsigned int jdport = JDPORT;
int jdprotocol = 0; /* use direct protocol if != 0 */
int nlpt = 0;
struct lpt_info lpt[MAXLPT];
int notone = 0;
int reinit = 0; /* reinitialise on abort */
unsigned int lpt_tone[MAXLPT] = { 523, 587, 659 }; /* CDE */
char lpt_names[MAXLPT][16] = { "lpt1", "lpt2", "lpt3" };
struct conn_info conn[MAXCON]; /* one extra for lpq/lprm requests */
struct conn_info log; /* for connection to syslogd */
char *loghost = 0;
int check_subnet = 1;
int nallow = 0, ndeny = 0;
longword allow[20], deny[20];
void (*normal_init)(char *name, char *value);
#define MAXALLOW (sizeof(allow)/sizeof(allow[0]))
#define MAXDENY (sizeof(deny)/sizeof(deny[0]))
extern longword my_ip_addr;
extern longword sin_mask;
#define ack_cmd(c) (void)sock_fastwrite(&c->sock, (byte *)"", 1)
#define nak_cmd(c) (void)sock_fastwrite(&c->sock, (byte *)"\001", 1)
char *ptime(void)
{
time_t t;
char *p;
t = time(0);
p = ctime(&t);
p[24] = '\0';
return (p);
}
void init_queues(void)
{
struct conn_info *c;
int i, ret;
struct lpt_info *p;
if (nlpt == 0) /* not overridden by -n? */
{
ret = biosequip();
nlpt = (ret >> 14) & 0x3;
}
for (i = 0; i < nlpt; ++i)
{
p = &lpt[i];
if (p->avail == DISABLED) /* unavail from cmd line? */
continue;
(void)bios_printer_init(i); /* initialise printer */
sleep(2); /* let printer settle */
p->status = bios_printer_status(i); /* get status */
if (p->status & (P_TIMEOUT|P_IOERROR))
{
p->avail = NONESUCH; /* printer not available */
(void)printf("%s: Error initialising %s\n",
ptime(), lpt_names[i]);
}
else
{
p->avail = FREE; /* printer available */
(void)printf("%s: %s initialised",
ptime(), lpt_names[i]);
if (p->hwaddr && (ret = peek(0, 0x408 + i * 2)) != 0)
(void)printf(", direct hardware access at %#3X",
p->hwaddr = ret);
(void)printf("\n");
}
}
for (i = 0; i < MAXLPT; ++i)
{
if (notone || lpt[i].avail != FREE)
continue;
sound(lpt_tone[i]);
sleep(1);
nosound();
}
for (i = 0; i < MAXCON; ++i)
{
c = &conn[i];
c->state = INIT; /* initial connection state */
c->controlfirst = 0;
c->printer = -1;
}
}
void init_log(void)
{
longword logip;
if (loghost == 0 ||
(logip = resolve(loghost)) == (longword)0 ||
udp_open(&log.sock, 0, logip, LOGPORT, NULL) == 0)
{
loghost = 0;
return;
}
(void)sprintf(log.buffer, LOG_TAG PROGRAM " " VERSION
", %d printer(s)\n", nlpt);
(void)sock_fastwrite(&log.sock, log.buffer, strlen(log.buffer));
}
void report_change(char *msg, int printer)
{
(void)printf("%s: %s %s\n", ptime(), lpt_names[printer], msg);
if (loghost != 0) /* log to syslogd */
{
(void)sprintf(log.buffer, LOG_TAG "%s %s\n", lpt_names[printer], msg);
(void)sock_fastwrite(&log.sock, log.buffer, strlen(log.buffer));
}
}
void lpt_status_change(int i, int delta, int status)
{
if (status & P_TIMEOUT)
report_change("time out", i);
if (status & P_IOERROR)
report_change("I/O error", i);
if (delta & P_SELECTED)
{
if (status & P_SELECTED)
report_change("online", i);
else
report_change("offline", i);
}
if (status & P_NOPAPER)
report_change("paper out", i);
}
int same_subnet(longword client)
{
/* assumes that bit operations can be done on longword */
return (((client ^ my_ip_addr) & sin_mask) == 0);
}
int in_list(longword ip, longword table[], int entries, int maxtable)
{
int i;
for (i = 0; i < entries && i < maxtable; ++i)
if (ip == table[i])
return (1);
return (0);
}
int check_access(longword ip)
{
if (check_subnet && !same_subnet(ip))
return (0);
if (nallow > 0)
return (in_list(ip, allow, nallow, MAXALLOW) ? 1 : 0);
if (ndeny > 0)
return (in_list(ip, deny, ndeny, MAXDENY) ? 0 : 1);
return (1);
}
/*
* Translate from name to printer number
*/
int printernumber(char *printername)
{
int i;
for (i = 0; i < MAXLPT; ++i)
if (stricmp(lpt_names[i], printername) == 0)
return (i);
return (-1);
}
void queuename(struct conn_info *c)
{
int i, current, delta;
char printername[sizeof(lpt_names[0])];
*c->bufip = '\0';
if (*c->buffer != '\002' && *c->buffer != '\003')
{
c->state = NAKANDCLOSE;
return;
}
if (sscanf(c->buffer+1, "%15s", printername) != 1 ||
(i = printernumber(printername)) < 0)
{
(void)printf("%s: Printer name error: %s", ptime(), c->buffer+1);
c->state = NAKANDCLOSE;
return;
}
switch (*c->buffer)
{
case '\002':
if (lpt[i].avail != FREE)
{
(void)printf("%s: Printer not available: %s",
ptime(), c->buffer+1);
c->state = NAKANDCLOSE;
return;
}
c->printer = i;
lpt[i].avail = BUSY;
(void)printf("%s: Job for %s started\n",
ptime(), lpt_names[i]);
c->state = RECVJOB;
ack_cmd(c);
break;
case '\003':
case '\004':
(void)sprintf(c->buffer, "%s is ", lpt_names[i]);
switch (lpt[i].avail)
{
case BUSY:
(void)strcat(c->buffer, "busy\n");
break;
case FREE:
(void)strcat(c->buffer, "available");
/* get status of printer and save it */
lpt[i].status = current = bios_printer_status(i);
(void)strcat(c->buffer, current & P_SELECTED ?
" online" : " offline");
if (current & P_NOPAPER)
(void)strcat(c->buffer, " no paper");
(void)strcat(c->buffer, "\n");
break;
default:
(void)strcat(c->buffer, "not available\n");
break;
}
(void)sock_fastwrite(&c->sock, c->buffer, strlen(c->buffer));
c->state = CLOSING;
break;
}
}
void get_jobinfo(struct conn_info *c)
{
char *p;
p = c->bufip;
if (buffer_room(c) == 0)
--p;
*p = '\0';
for (p = strtok(c->buffer, "\n"); p != 0; p = strtok(0, "\n"))
{
switch (*p)
{
case 'N':
(void)strncat(c->jobname, p+1, sizeof(c->jobname)-1);
break;
case 'P':
(void)strncat(c->username, p+1, sizeof(c->username)-1);
break;
case 'H':
(void)strncat(c->hostname, p+1, sizeof(c->hostname)-1);
break;
}
}
}
void read_bytes(struct conn_info *c)
{
int count;
char *p;
if ((count = sock_rbused(&c->sock)) > 0)
{
if (count > buffer_room(c))
count = buffer_room(c);
(void)sock_fastread(&c->sock, (byte *)c->bufip, count);
c->bufip += count;
}
if (!data_unseen(c))
return;
nextstate:
switch (c->state)
{
case QUEUENAME:
queuename(c);
reset_ptrs(c);
break;
case RECVJOB:
switch (*c->buffer)
{
case '\001':
ack_cmd(c);
c->state = CLOSING;
break;
case '\002':
c->state = CONTROLINFO;
c->controlfirst = 1;
goto nextstate;
case '\003':
c->state = DATAINFO;
goto nextstate;
}
case CONTROLINFO:
case DATAINFO:
p = c->bufip;
if (buffer_room(c) == 0)
--p;
*p = '\0';
if (sscanf(c->buffer+1, "%ld", &c->bytelen) != 1)
{
(void)printf("%s: %s, error parsing %s",
ptime(), lpt_names[c->printer], c->buffer+1);
c->state = NAKANDCLOSE;
return;
}
++c->bytelen; /* for EOF byte */
ack_cmd(c);
c->jobname[0] = '\0';
c->username[0] = '\0';
c->hostname[0] = '\0';
c->state = c->state == CONTROLINFO ? CONTROL : DATA;
if (c->state == DATA)
c->starttime = time(0);
reset_ptrs(c);
break;
case CONTROL:
/* copy out interesting information from buffer */
get_jobinfo(c);
c->bytelen -= count;
if (c->bytelen <= 0)
{
(void)printf("%s: Job ", ptime());
if (c->jobname[0] != '\0')
(void)printf("%s ", c->jobname);
if (c->username[0] != '\0' && c->hostname[0] != '\0')
(void)printf("for %s@%s ", c->username,
c->hostname);
(void)printf("on %s\n", lpt_names[c->printer]);
ack_cmd(c);
c->state = c->controlfirst ? DATAINFO : CLOSING;
}
reset_ptrs(c);
break;
case DATA:
c->bytelen -= count;
if (c->bytelen <= 0 && count > 0) /* skip EOF byte */
--c->bufip;
c->state = PRINTING;
break;
}
}
/*
* Direct hardware write to printer port
*/
int printer_outbuf(int port, char *buffer, int count)
{
int i, printed, status;
printed = 0;
inportb(port+1); /* read status */
status = (inportb(port+1) & 0xf8) ^ 0x48;
while (count > 0 && (status & P_READY) == P_READY)
{
outportb(port, *buffer++); /* write character */
outportb(port+2, 0x0d); /* raise strobe */
outportb(port+2, 0x0c); /* lower strobe */
++printed;
--count;
/* sample the busy line for a few tries */
for (i = 0; i < POLL_MAX; ++i)
{
status = (inportb(port+1) & 0xf8) ^ 0x48;
if ((status & P_READY) == P_READY)
break;
}
}
return (printed);
}
void print_data(struct conn_info *c)
{
int printer, current, delta, printed;
struct lpt_info *p;
p = &lpt[printer = c->printer];
current = bios_printer_status(printer);
/* report changes from last time */
delta = (current ^ p->status) & P_CHANGES;
if (delta)
lpt_status_change(printer, delta, current);
p->status = current;
/* if printer is busy, try to collect more data */
if ((current & P_READY) != P_READY && buffer_room(c) > 0)
{
c->state = DATA;
return;
}
if (p->hwaddr != 0)
{
printed = printer_outbuf(p->hwaddr, c->bufop,
c->bufip - c->bufop);
c->bufop += printed;
}
else
{
printed = 0;
/* loop, printing as much as possible */
/* eat your heart out, Pascal */
while (c->bufop < c->bufip &&
((current & P_READY) == P_READY ||
(bios_printer_status(printer) & P_READY) == P_READY))
{
current = bios_printer_outch(printer, *c->bufop++);
++printed;
}
}
if (!data_remaining(c))
{
reset_ptrs(c);
/* in jdprotocol bytelen will not be 0 before EOF
well, not before maxlong bytes anyway */
if (c->bytelen > 0)
c->state = DATA;
else
{
ack_cmd(c);
c->state = c->controlfirst ? CLOSING : CONTROLINFO;
}
}
c->joblen += printed;
}
void show_stats(struct conn_info *c)
{
time_t elapsed;
elapsed = time(0) - c->starttime;
(void)printf("%s: %s: %ld bytes %ld seconds", ptime(),
lpt_names[c->printer], c->joblen, elapsed);
if (elapsed > 0)
(void)printf(" %ld bytes/second", c->joblen / elapsed);
(void)printf("\n");
}
void loop(int port, struct conn_info *c)
{
unsigned int cport;
struct sockaddr client;
int i;
char *name;
switch (c->state) {
case INIT:
if (jdprotocol)
{
if (port >= MAXLPT || lpt[port].avail != FREE)
break; /* don't offer connection */
cport = jdport + port;
c->printer = port;
}
else
cport = lpdport;
tcp_listen(&c->sock, cport, 0L, 0, NULL, 0);
(void)printf("%s: Connection %d listening on TCP port %u\n",
ptime(), port, cport);
c->state = WAITING;
break;
case WAITING:
tcp_tick(NULL);
if (!sock_established(&c->sock))
break;
i = sizeof(client);
client.s_ip = 0;
name = getpeername(&c->sock, &client, &i) == 0 ?
inet_ntoa(c->buffer, client.s_ip) : "?";
if (check_access(client.s_ip))
{
(void)printf("%s: Connection %d from %s\n",
ptime(), port, name);
c->state = jdprotocol ? DATA : QUEUENAME;
c->bytelen = 0x7ffffff; /* maxlong */
c->starttime = time(0);
c->joblen = 0L;
}
else
{
(void)printf("%s: Connection %d from %s refused\n",
ptime(), port, name);
c->state = CLOSING;
}
reset_ptrs(c);
break;
case QUEUENAME:
case RECVJOB:
case CONTROLINFO:
case CONTROL:
case DATAINFO:
case DATA:
if (tcp_tick(&c->sock))
read_bytes(c);
/* don't shutdown until all printed */
else if (data_remaining(c))
c->state = PRINTING;
else
c->state = CLOSING;
break;
case PRINTING:
if (tcp_tick(&c->sock) || data_remaining(c))
print_data(c);
else
c->state = CLOSING;
break;
case NAKANDCLOSE:
nak_cmd(c);
case CLOSING:
show_stats(c);
sock_flush(&c->sock);
sock_close(&c->sock);
reinit:
/* free up printer */
if (0 <= c->printer && c->printer < MAXLPT
&& lpt[c->printer].avail == BUSY)
lpt[c->printer].avail = FREE;
c->state = INIT;
c->controlfirst = 0;
c->printer = -1;
break;
case ABORT:
sock_abort(&c->sock);
goto reinit;
}
}
void check_key(void)
{
int key, i;
if (((key = bioskey(0)) & 0xff) != 0)
return; /* ASCII key */
key = (key >> 8) & 0xff;
if (key < CF1 || key > CF3)
return;
key -= CF1; /* which printer? */
for (i = 0; i < MAXCON; ++i)
{
if (conn[i].printer == key && conn[i].state != INIT)
{
if (reinit)
{
(void)printf("%s: Aborting job and reinitialising %s\n",
ptime(), lpt_names[key]);
(void)biosprint(1, 0, key);
sleep(2); /* let printer settle */
}
else
{
(void)printf("%s: Aborting job on %s\n",
ptime(), lpt_names[key]);
}
conn[i].state = ABORT; /* abort job */
return;
}
}
}
void make_list(char *adjective, char *s,
longword table[], int *nentries, int maxtable)
{
char *p;
longword ip;
int i;
char buffer[64];
for ( ; *s != '\0'; s = p + 1)
{
if ((p = strchr(s, ',')) == 0)
p = s + strlen(s) - 1;
else
*p = '\0'; /* mark end */
if ((ip = resolve(s)) == (longword)0)
continue;
if (*nentries >= maxtable)
continue; /* should print warning */
table[(*nentries)++] = ip;
}
for (i = 0; i < *nentries; ++i)
(void)printf("%s ", inet_ntoa(buffer, table[i]));
if (*nentries > 0)
(void)printf("%s access\n", adjective);
}
void my_init(char *name, char *value)
{
if (strcmp(name, "PRINTER1NAME") == 0)
{
strncpy(lpt_names[0], value, sizeof(lpt_names[0]));
lpt_names[0][sizeof(lpt_names[0])-1] = '\0';
}
else if (strcmp(name, "PRINTER2NAME") == 0)
{
strncpy(lpt_names[1], value, sizeof(lpt_names[1]));
lpt_names[1][sizeof(lpt_names[1])-1] = '\0';
}
else if (strcmp(name, "PRINTER3NAME") == 0)
{
strncpy(lpt_names[2], value, sizeof(lpt_names[2]));
lpt_names[2][sizeof(lpt_names[2])-1] = '\0';
}
else if (normal_init)
(*normal_init)(name, value);
}
void options(int argc, char **argv)
{
char *s;
int i;
for (i = 0; i < MAXLPT; ++i)
lpt[i].hwaddr = 0;
for (--argc, ++argv; argc > 0 && *argv[0] == '-'; --argc, ++argv)
{
s = argv[0];
switch (*++s)
{
case '1': case '2': case '3':
for ( ; '1' <= *s && *s <= '3'; ++s)
lpt[*s - '1'].avail = DISABLED;
break;
case 'a':
if (ndeny <= 0)
make_list("allowed", ++s, allow, &nallow,
MAXALLOW);
break;
case 'b':
++s;
for ( ; '1' <= *s && *s <= '3'; ++s)
lpt[*s - '1'].hwaddr = 1;
break;
case 'd':
if (nallow <= 0)
make_list("denied", ++s, deny, &ndeny,
MAXDENY);
break;
case 'i':
reinit = 1;
break;
case 'j':
jdprotocol = 1;
if ((jdport = atoi(++s)) <= 0)
jdport = JDPORT;
break;
case 'l':
loghost = ++s;
break;
case 'n':
if ((nlpt = atoi(++s)) > MAXLPT || nlpt < 0)
nlpt = 0;
break;
case 'p': /* alternate TCP port */
if ((lpdport = atoi(++s)) <= 0)
lpdport = LPDPORT;
break;
case 's':
check_subnet = 0;
break;
case 't':
notone = 1;
break;
}
}
}
int main(int argc, char **argv)
{
int i;
(void)printf(PROGRAM " " VERSION " " AUTHOR "\n");
(void)printf(PROGRAM " comes with ABSOLUTELY NO WARRANTY; for details read the file COPYING\n");
(void)printf("This is free software, and you are welcome to redistribute it\n");
(void)printf(" under certain conditions; see the file COPYING for details.\n");
(void)printf("C-F1 through C-F3 to abort printer jobs\n");
(void)memset(conn, 0, sizeof(conn));
(void)memset(lpt, 0, sizeof(lpt));
tzset();
options(argc, argv);
/* hook onto init procedure to get NAME=VALUE pairs */
normal_init = usr_init;
usr_init = my_init;
dbuginit();
sock_init();
init_queues();
if (nlpt == 0)
{
(void)printf("%s: No printers connected\n", ptime());
return (1);
}
init_log();
for (;;)
{
for (i = 0; i < MAXCON; ++i)
loop(i, &conn[i]);
if (kbhit())
check_key();
}
/*NOTREACHED*/
return (0);
}